﻿#include "editor/whiteboard.h"
#include "ui/native_dialogs.h"
#include "editor/settings.h"
#include "target.h"
#include "texture.h"
#include <fstream>

using namespace std;
using namespace anip;

class editor::Whiteboard::Tools : public ui::Window
{
private:
	ui::Slider* s1, * s2, * s3, * s4, * st, * zoom;
	ui::Joystick* pan;
	ui::Button* ind;
	ui::RadioBar* sel;

	void setSize(invec2 size)
	{
		postSignal({ ui::Signal::Type::NeedsReflow });
		Window::setSize(size);
	}

public:
	function<void()> clear, save, load;

	float paramWidth() const
	{
		return st->value();
	}

	color paramColor() const
	{
		return ind->normalColor;
	}

	bool isPen() const
	{
		return sel->selectedIndex() == 0;
	}

	float zoomer() const
	{
		return zoom->value();
	}

	vec2 panner() const
	{
		return pan->value();
	}

	Tools(ui::Container* p, invec2 size = { 100, 300 }) :
		Window(p, size)
	{
		this->_flowColFac = 0.1f;
		setFont(editor::Settings::headerFont);
		setInheritFont(editor::Settings::defaultFont);
		setText(L"设置");

		auto btn = ui::FlowHelper::button(*this, L"加载");
		btn->layoutOptions.stretchX = 1;
		btn->onClick = [](ui::ButtonBase& b)
		{
			Tools* t = (Tools*)b.parent();
			t->load();
		};
		ui::FlowHelper::endRow(*this);

		btn = ui::FlowHelper::button(*this, L"保存");
		btn->layoutOptions.stretchX = 1;
		btn->onClick = [](ui::ButtonBase& b)
		{
			Tools* t = (Tools*)b.parent();
			t->save();
		};
		ui::FlowHelper::endRow(*this);

		btn = ui::FlowHelper::button(*this, L"清除");
		btn->layoutOptions.stretchX = 1;
		btn->onClick = [](ui::ButtonBase& b)
		{
			Tools* t = (Tools*)b.parent();
			t->clear();
		};
		ui::FlowHelper::endRow(*this);

		ui::FlowHelper::beginRow(*this, L"粗细");
		st = ui::FlowHelper::slider(*this, 0, 0.1f, 0.01f);
		st->layoutOptions.stretchX = 1;
		ui::FlowHelper::endRow(*this);

		ui::FlowHelper::beginRow(*this, L"R");
		s1 = ui::FlowHelper::slider(*this, 0, 1, 0);
		s1->layoutOptions.stretchX = 1;
		ui::FlowHelper::endRow(*this);

		ui::FlowHelper::beginRow(*this, L"G");
		s2 = ui::FlowHelper::slider(*this, 0, 1, 0);
		s2->layoutOptions.stretchX = 1;
		ui::FlowHelper::endRow(*this);

		ui::FlowHelper::beginRow(*this, L"B");
		s3 = ui::FlowHelper::slider(*this, 0, 1, 0);
		s3->layoutOptions.stretchX = 1;
		ui::FlowHelper::endRow(*this);

		ui::FlowHelper::beginRow(*this, L"A");
		s4 = ui::FlowHelper::slider(*this, 0, 1, 1);
		s4->layoutOptions.stretchX = 1;
		ui::FlowHelper::endRow(*this);

		ind = ui::FlowHelper::button(*this, L"");
		ind->normalColor = { 0, 0, 0, 1 };
		ind->layoutOptions.stretchX = 1;
		ind->layoutOptions.minSize = { 0, 30 };
		ind->enabled = false;
		ui::FlowHelper::endRow(*this);

		static const wstring names[2] = { L"笔", L"橡皮" };
		sel = ui::FlowHelper::radioBar(*this, 2, names);
		sel->items[0].select();
		ui::FlowHelper::endRow(*this);

		zoom = ui::FlowHelper::slider(*this, 0.5f, 1.5f, 1);
		zoom->defaultValue = 1;
		zoom->spring = 0.06f;
		zoom->vertical = true;

		pan = ui::FlowHelper::joystick(*this);
		pan->spring = 0.06f;
		ui::FlowHelper::endRow(*this);
		
		auto proc = [](ui::Slider& s)
		{
			auto t = (Tools*)s.parent();
			t->ind->normalColor = {
				t->s1->value(),
				t->s2->value(),
				t->s3->value(),
				t->s4->value() };
		};

		s1->onValueChanged = proc;
		s2->onValueChanged = proc;
		s3->onValueChanged = proc;
		s4->onValueChanged = proc;

		postSignal({ ui::Signal::Type::NeedsReflow });
	}
};

void editor::Whiteboard::render()
{
	cxt.beginRender(*bufMultisample);

	Context::clear(Colors::white);
	renderer->render(cxt);

	bufMultisample->blitTo(*bufRestore);
	cxt.endRender();

	postSignal({ ui::Signal::Type::NeedsRedraw });
}

void editor::Whiteboard::addPoint()
{
	if (current == nullptr)
		return;

	vg::Stroke::Point p;
	p.position = mp;
	p.color = tools->paramColor();
	p.width = tools->paramWidth() / sqrtf(1.0f + velocity);
	current->points.push_back(p);
	current->calcNormal();
	current->markEdit();
	renderer->update();
}

void editor::Whiteboard::beginStroke()
{
	if (current != nullptr)
		return;
	current = new vg::Stroke();
	group->primitives.push_back(current);
	renderer->update();
}

void editor::Whiteboard::finishStroke()
{
	if (current != nullptr)
	{
		current = nullptr;
		render();
	}
}

void editor::Whiteboard::paint()
{
	if (abs(1 - tools->zoomer()) > 0.001f || !(tools->panner() == vec2{0}))
	{
		postSignal({ ui::Signal::Type::Animation });
		cxt.transform.zoom *= powf(tools->zoomer(), (float)-Context::deltaTime());
		cxt.transform.offset += 
			tools->panner() * vec2 { -0.005f, 0.005f } / cxt.transform.zoom;
		render();
	}
	//else markNeedAnimate(false);

	Window::paint();
}

editor::Whiteboard::Whiteboard(Container* parent, invec2 size) :
	EditorWindow(parent, size, L"白板")
{
	auto& btn = addCloseButton();
	btn.texture = Settings::closeButtonTexture.get();

	canvas = new ui::Draggable<ui::Canvas>();

	group = new vg::VgFrameGroup();
	renderer = group->createRenderer(); 
	renderer->activate();

	vg::Stroke* str = new vg::Stroke();
	str->points.push_back({ vec2{0} });
	str->points.push_back({ vec2{0.5f} });
	str->calcNormal();
	group->primitives.push_back(str);
	renderer->update();

	// create buffers
	
	vec2 s = Context::findCurrentMonitor().resolution();
	bufMultisample = new RenderBuffer((int)s.x, (int)s.y, 4);
	bufRestore = new RenderBuffer((int)s.x, (int)s.y);

	cxt = GraphicsRenderContext(s);
	render();

	tools = new Tools(this, { 250, 600 });
	tools->setPosition({ 50, 50 });
	tools->clear = [this]()
	{
		if (current != nullptr)
		{
			delete current;
			current = nullptr;
		}
		for (auto i : group->primitives)
			delete i;
		group->primitives.clear();
		renderer->update();
		render();
	};
	tools->save = [this]()
	{
		wstring file = ui::native::saveFileDialog(L"\\", L"", {
			{L"All files"s, L"*.*"s},
			{L"Frame group (*.anipf)"s, L"*.anipf"s} }, 1, L"anipf");
		if (file.empty()) return;
		ofstream out(fs::ws2path(file), ios::out | ios::binary);
		anip::serialize<FrameGroup>(*group, out);
		out.close();
	};
	tools->load = [this]()
	{
		wstring file = ui::native::openFileDialog(L"\\", L"", {
			{L"All files"s, L"*.*"s},
			{L"Frame group (*.anipf)"s, L"*.anipf"s} }, 1);
		if (file.empty()) return;

		delete renderer;
		delete group;

		ifstream in(fs::ws2path(file), ios::in | ios::binary);
		group = (vg::VgFrameGroup*) anip::deserialize<FrameGroup>(in);
		in.close();
		renderer = group->createRenderer();
		renderer->activate();
		cxt.transform = Transform2D();
		render();
	};
	tools->setPosition({ 0, 0 });

	addChild(*canvas);
	canvas->setPosition({ 0, 0 });
	canvas->setSize(_size - _margin);
	//canvas->layoutOptions.dock = Control::LayoutOptions::DockType::Fill;
	canvas->texture = &bufRestore->getTexture();

	canvas->onMouseMove = [this](ui::Control& self, invec2 pos)
	{
		vec2 newp = cxt.screen2view(pos);
		/*vec2{ pos.x, view.aspectScale - pos.y } /
			(view.aspectScale * view.zoom) - view.offset;*/
		if (math::length((newp - mp) * cxt.transform.zoom) > 0.003f)
		{
			lp = mp;
			mp = newp;
			ltime = mtime;
			mtime = (float)anip::time();
			velocity = velocity * 0.8f +
				math::distance(mp, lp) / (mtime - ltime) * 0.2f;
			if (!mdown) return;

			if (tools->isPen())
			{
				addPoint();
				render();
			}
			else for (auto i = group->primitives.cbegin();
						i != group->primitives.cend(); i++)
				if ((*i)->isInside(mp, 0))
				{
					vg::Primitive* p = *i;
					group->primitives.erase(i);
					delete p;
					renderer->update();
					render();
					break;
				}
		}
	};

	canvas->onMouseButton = [this](Control&, MouseButton btn, ButtonAction action)
	{
		if (btn != MouseButton::Left) return;

		if (action == ButtonAction::Down)
		{
			mdown = true;
			if (tools->isPen())
			{
				beginStroke();
				addPoint();
			}
		}
		else if (action == ButtonAction::Up)
		{
			mdown = false;
			if (tools->isPen())
			{
				addPoint();
				finishStroke();
				render();
			}
		}
	};

	canvas->onMouseLeave = [this](Control&)
	{
		mdown = false;
		finishStroke();
	};

	canvas->onBeginDrag = [this](ui::Draggable<ui::Canvas>& c, MouseButton btn, invec2 pos)
	{
		if (btn != MouseButton::Left)
		{
			oldOffset = cxt.transform.offset;
			return true;
		}
		return false;
	};

	canvas->onDrag = [this](ui::Draggable<ui::Canvas>& c, invec2 start, invec2 pos)
	{
		cxt.transform.offset = oldOffset + 
			(pos - start) * vec2{ 1, -1 } / cxt.aspectScale();
		render();
	};

	canvas->onScroll = [this](ui::Canvas& c, invec2 pos)
	{
		cxt.transform = cxt.transform.scaleAround(
			cxt.screen2view((_size - _margin) * 0.5f), 
			vec2{ 1.0f + pos.y * 0.05f });
		render();
	};
}

void anip::editor::Whiteboard::setSize(invec2 size)
{
	EditorWindow::setSize(size);

	canvas->setSize(_size - _margin);
	if (size.x > bufRestore->width() || size.y > bufRestore->height())
	{
		// anip_fixme: any better solutions?

		bufMultisample->finalize();
		bufRestore->finalize();
		delete bufMultisample;
		delete bufRestore;

		vec2 s = Context::findCurrentMonitor().resolution();
		if (s.x < size.x) s.x = size.x;
		if (s.y < size.y) s.y = size.y;

		bufMultisample = new RenderBuffer((int)s.x, (int)s.y, 4);
		bufRestore = new RenderBuffer((int)s.x, (int)s.y);
		cxt.setViewport(s);
	}
	
	//view.aspectRatio = _size.x / _size.y;
	//view.aspectScale = _size.y;
	render();
	canvas->texture = &bufRestore->getTexture();
}

editor::Whiteboard::~Whiteboard()
{
	bufMultisample->finalize();
	bufRestore->finalize();
	delete bufMultisample, bufRestore;
}